#pragma once

#include <vector>
#include <string>
#include <functional>
#include <list>
#include <boost/range/iterator_range.hpp>
#include <boost/unordered_map.hpp>
#include <boost/cstdint.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <fstream>
#include <memory>

/* Typedef */
typedef size_t vertex_index_t;
typedef int bucket_index_t;

/* vertex_id */
struct vertex_id
{
  vertex_id(vertex_index_t v_id, bucket_index_t b_id) :
    vertex_index(v_id), bucket_index(b_id)
  {
  }

  vertex_id(vertex_id const & rhs) :
    vertex_index(rhs.vertex_index), bucket_index(rhs.bucket_index)
  {
  }

  vertex_id(vertex_id const volatile & rhs) :
    vertex_index(rhs.vertex_index), bucket_index(rhs.bucket_index)
  {
  }

  vertex_id & operator =(vertex_id const & rhs)
  {
    using namespace std;
    if (this != &rhs)
      vertex_id(rhs).swap(*this);
    return *this;
  }

  void swap(vertex_id & v)
  {
    std::swap(vertex_index, v.vertex_index);
    std::swap(bucket_index, v.bucket_index);
  }

  bucket_index_t bucket_index;
  vertex_index_t vertex_index;
};

size_t hash_value(vertex_id v);

bool operator ==(vertex_id const & lhs, vertex_id const & rhs);

bool operator !=(vertex_id const & lhs, vertex_id const & rhs);

void swap(vertex_id & lhs, vertex_id & rhs);

namespace std
{
  template<>
  inline void swap<vertex_id> (vertex_id & lhs, vertex_id & rhs)
  {
    ::swap(lhs, rhs);
  }
}

/* adj_vertex */
struct adj_vertex
{
  adj_vertex(vertex_id vertex_index, float weight) :
    vertex_index(vertex_index), weight(weight)
  {
  }
  vertex_id vertex_index;
  float weight;
};

/* geodata_t */
struct geodata_t
{
  geodata_t() :
    lat(0), lon(0)
  {
  }
  geodata_t(float lat, float lon) :
    lat(lat), lon(lon)
  {
  }
  float lat;
  float lon;
};

// adj_vertex_iterator
namespace details
{
  struct bucket_t;
}

struct adj_vertex_const_iterator_t
{
  adj_vertex_const_iterator_t(boost::shared_ptr<details::bucket_t> bucket,
                              adj_vertex volatile const * adj) :
    bucket_ptr_(bucket), data_ptr_(adj)
  {
  }

  adj_vertex_const_iterator_t(adj_vertex_const_iterator_t const & c) :
    bucket_ptr_(c.bucket_ptr_), data_ptr_(c.data_ptr_)
  {
  }

  adj_vertex_const_iterator_t operator =(
                                         adj_vertex_const_iterator_t const & rhs)
  {
    if (this != &rhs)
      adj_vertex_const_iterator_t(rhs).swap(*this);
  }

  adj_vertex const volatile & operator *() const
  {
    return *data_ptr_;
  }

  void swap(adj_vertex_const_iterator_t & rhs)
  {
    std::swap(this->bucket_ptr_, rhs.bucket_ptr_);
    std::swap(this->data_ptr_, rhs.data_ptr_);
  }

  adj_vertex_const_iterator_t & operator --()
  {
    --data_ptr_;
    return *this;
  }

  adj_vertex_const_iterator_t operator --(int)
  {
    adj_vertex_const_iterator_t tmp(*this);
    --(*this);
    return tmp;
  }

  adj_vertex_const_iterator_t & operator ++()
  {
    ++data_ptr_;
    return *this;
  }

  adj_vertex_const_iterator_t operator ++(int)
  {
    adj_vertex_const_iterator_t tmp(*this);
    ++(*this);
    return tmp;
  }

  adj_vertex const volatile * operator ->() const
  {
    return data_ptr_;
  }

  friend bool operator ==(adj_vertex_const_iterator_t const & lhs,
                          adj_vertex_const_iterator_t const & rhs);
private:
  boost::shared_ptr<details::bucket_t> bucket_ptr_;
  adj_vertex volatile const * data_ptr_;
};

bool operator ==(adj_vertex_const_iterator_t const & lhs,
                 adj_vertex_const_iterator_t const & rhs)
{
  return lhs.data_ptr_ == rhs.data_ptr_;
}

bool operator !=(adj_vertex_const_iterator_t const & lhs,
                 adj_vertex_const_iterator_t const & rhs)
{
  return !(lhs == rhs);
}

typedef boost::iterator_range<adj_vertex_const_iterator_t> adj_vertices_range_t;

namespace details
{
  typedef boost::unordered_map<vertex_index_t, geodata_t> data_map_t;
  typedef std::vector<adj_vertex> adj_list_t;
  typedef boost::unordered_map<vertex_index_t, adj_list_t> adj_map_t;

  struct raw_bucket_t
  {
    uint32_t vertices_count;
    uint8_t data[];
  };

  struct raw_vertex_t
  {
    float lon;
    float lat;
    uint32_t edges_offset;
  };

  struct bucket_t
  {
    bucket_t(bucket_index_t bucket_id, void * raw_bucket);
    ~bucket_t();

    adj_vertices_range_t adjacent_range(vertex_index_t v) const;
    geodata_t get_data(vertex_index_t v) const;
    bucket_index_t bucket_id() const
    {
      return bucket_id_;
    }
    size_t get_size() const
    {
      return raw_bucket_->vertices_count;
    }
  private:
    static const size_t v_rawsize = sizeof(volatile raw_vertex_t);
    static const size_t e_rawsize = sizeof(volatile adj_vertex);

    bucket_index_t bucket_id_;
    volatile raw_bucket_t * raw_bucket_;
  };

  struct bucket_fileloader_t
  {
    bucket_fileloader_t(std::string const & bucket_path = std::string("")) :
      path_(bucket_path)
    {
      std::fill(times, times + 10000, 0);
    }

    ~bucket_fileloader_t()
    {
      for (size_t i = 0; i < 10000; ++i)
      {
        std::cout << i << " " << times[i] << "\n";
      }
    }

    void * load_bucket(bucket_index_t bucket_id)
    {
      assert(bucket_id < 100);
      ++times[bucket_id];
      std::ifstream
          input((path_ + boost::lexical_cast<std::string>(bucket_id)).c_str(),
                std::ios::in | std::ios::binary);

      input.seekg(0, std::ios::end);
      size_t size = input.tellg();
      input.seekg(0, std::ios::beg);

      void * data = malloc(size);
      input.read(reinterpret_cast<char *> (data), size);

      return data;
    }
  private:
    std::string path_;
    size_t times[10000];
  };

  struct bucket_manager_t
  {
    bucket_manager_t(std::string const & path_to_buckets, size_t buckets_count);
    bucket_t const & get_bucket(bucket_index_t bucket_id);
  private:
    typedef std::list<boost::shared_ptr<bucket_t> > bucket_storage_t;
    typedef boost::unordered_map<bucket_index_t, bucket_storage_t::iterator>
        bucket_index_storage_t;

    boost::shared_ptr<bucket_t> load_bucket(bucket_index_t bucket_id);
    void drop_bucket();

    bucket_fileloader_t bucket_loader_;
    bucket_storage_t buckets_;
    bucket_index_storage_t bucket_index_;
    size_t max_buckets_;
  };
}

struct db_graph_t
{
  db_graph_t(std::string const & buckets_path);

  //  vertex_id add_vertex(geodata_t const & data);
  //  void add_edge(vertex_id src, vertex_id dst, float w);

  void remove_vertex(vertex_id v);
  void remove_edge(vertex_id src, vertex_id dst);

  adj_vertices_range_t adjacent_range(vertex_id v) const;
  geodata_t get_data(vertex_id v) const;
private:
  void load_db(std::string const & db_init);

  void add_vertex_with_id(vertex_id id, geodata_t const & data);

  mutable details::bucket_manager_t bucket_manager_;
};
